# -*- coding: utf-8 -*-

from datetime import date
from random import random
from time import time
from hashlib import md5
import datetime
from django.utils.dateformat import format

from django.db.models import Q
from core.models import ProtectedModel
from core.utils import get_group

from django.db import models
from django.contrib.auth.models import User
from django.conf import settings

from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.exceptions import ValidationError

from django.db import transaction

from django.contrib.auth.models import User

class Professor(ProtectedModel):
  code = models.CharField(max_length = 6, primary_key = True, verbose_name = u'Šifra')
  first_name = models.CharField(max_length = 30, verbose_name = 'Ime')
  last_name = models.CharField(max_length = 30, verbose_name = 'Priimek')
  middle_name = models.CharField(max_length = 30, blank = True, verbose_name = 'Vmesno ime')
  title = models.CharField(max_length = 30, blank = True, verbose_name = 'Naziv')
  birth_date = models.DateField(blank = True, null = True, verbose_name = 'Rojstni datum')
  emso = models.CharField(max_length = 13, blank = True, verbose_name = u'EMŠO')
  permanent_street = models.CharField(max_length = 200, blank = True, verbose_name = 'Ulica in hišna številka')
  permanent_postal_number = models.ForeignKey('core.Post', blank = True, null = True, verbose_name = u'Pošta', limit_choices_to = {'inactive': False})
  permanent_commune = models.ForeignKey('core.Commune', blank = True, null = True, verbose_name = u'Občina', limit_choices_to = {'inactive': False})
  user = models.OneToOneField(User, related_name = 'professor', null = True, editable = False)
  inactive = models.BooleanField(default = False, verbose_name = 'Neveljaven')
  
  class Meta:
    permissions = (
      ('can_student_list', u'Lahko izpiše seznam študentov'),
      ('can_input_exam', u'Lahko vnaša nov izpitni rok'),
    )
    
    verbose_name = u'Profesor'
    verbose_name_plural = u'Profesorji'
    ordering = ['last_name', 'first_name']
  
  def check_deps(self):
    if self.executor_set.count() > 0:
      return False
    return True
  
  @transaction.commit_on_success
  def save(self, *args, **kwargs):
    super(Professor, self).save(*args, **kwargs)
    
    # Let's add our professor
    if self.user is None:
      self.user = User.objects.create_user(self.pk, '', self.pk)
      self.user.groups.add(get_group('professors'))
      
      # Connect the professor with the profile
      profile = self.user.get_profile()
      profile.professor = self
      profile.save()
    
    # Sync some settings
    self.user.first_name = self.first_name
    self.user.last_name = self.last_name
    self.user.save()
    
    # Save itself, again
    super(Professor, self).save(*args, **kwargs)

  def __unicode__(self):
    return u'prof. %s %s' % (self.first_name, self.last_name)

class Executor(models.Model):
  professor = models.ForeignKey('Professor', verbose_name = 'Profesor')
  subject_year = models.ForeignKey('SubjectYear', verbose_name = 'Letni predmet')
  is_holder = models.BooleanField(editable = False, default = False)
  cooperates_with = models.ForeignKey('Executor', blank = True, null = True, verbose_name = 'Sodeluje z', help_text = u'Tukaj določite nosilnega izvajalca, če je polje prazno, je nosilec predmeta')
  
  @transaction.commit_on_success
  def save(self, *args, **kwargs):
    # Make sure there won't be any transitional linkages
    if self.cooperates_with is not None:
      self.cooperates_with = self.cooperates_with.cooperates_with if self.cooperates_with.cooperates_with is not None else self.cooperates_with
      
      # And fix the ones already linked to this one
      for soexecutor in Executor.objects.filter(cooperates_with = self):
        soexecutor.cooperates_with = self.cooperates_with
        soexecutor.save()
    
    # Save it
    super(Executor, self).save(*args, **kwargs)
  
  def cooperates_with_str(self):
    return u", ".join((unicode(i) for i in self.cooperates_with_professors()))
  
  def cooperates_with_professors(self):
    group = [self.professor]
    
    for x in Executor.objects.filter(cooperates_with = self.head(), subject_year = self.subject_year):
      group.append(x.professor)
    return group
  
  def head(self):
    """This is not actually head of the group, but more as a linking executor
    """
    if self.cooperates_with:
      return self.cooperates_with
    return self
  
  @staticmethod
  def get_all_groups(add_id = False):
    """Returns tuple of all executors groups.
      add_id: If true, it will add to the first element in all tuples also subject id and school year
    """
    output = []
    
    for main_executor in Executor.objects.filter(cooperates_with = None):
      group = [main_executor]
      group += list(Executor.objects.filter(cooperates_with = main_executor))
      pk = unicode(main_executor.pk)
      if add_id is True:
        pk += u'-{0}-{1}'.format(main_executor.subject_year.subject.pk, main_executor.subject_year.school_year(True))
      output.append((pk, ', '.join((unicode(i.professor) for i in group))))
    
    return tuple(output)
  
  def __unicode__(self):
    return '%s, %s' % (unicode(self.professor), unicode(self.subject_year))
  
  class Meta:
    verbose_name = 'Izvajalec'
    verbose_name_plural = 'Izvajalci'
      
class SubjectYear(models.Model):
  professors = models.ManyToManyField('Professor', through = 'Executor', verbose_name = 'Profesorji')
  participants = models.ManyToManyField('student.Student', through = 'SubjectParticipant', verbose_name = 'Udeleženci')
  subject = models.ForeignKey('Subject', verbose_name = 'Predmet')
  school_year_start = models.IntegerField(verbose_name = 'Začetno leto šolskega leta')
  school_year_end = models.IntegerField(verbose_name = 'Končno leto šolskega leta')
  rating_type = models.CharField(default="OC1",max_length = 30, choices = settings.RATE_TYPES,verbose_name="Tip ocene")  
  
  def school_year(self, shorter = False):
    end_part = str(self.school_year_end) if shorter is False else str(self.school_year_end)[2:]
    return u'{0}/{1}'.format(self.school_year_start, end_part)
      
  def __unicode__(self):
    return u'%s %d/%d' %(self.subject, self.school_year_start, self.school_year_end)
  
  def executors(self):
    output = []
    
    for main_executor in self.executor_set.filter(cooperates_with = None):
      group = []
      # Add the main executor to the list
      group.append(main_executor)
      
      # Add others
      group += list(self.executor_set.filter(cooperates_with = main_executor))
      
      output.append(tuple(group))
    
    return output
  
  def executors_choices(self):
    """Returns a compatible tuple for using in ChoiceField.
    """
    output = []
    for executors_group in self.executors():
      output.append((executors_group[0].pk, ', '.join((unicode(i.professor) for i in executors_group))))
    
    return tuple(output)
  
  def executors_str(self):
    """Returns a list of strings.
    """
    return [', '.join((unicode(executor.professor) for executor in group)) for group in self.executors()]
  
  class Meta:
    verbose_name = 'Letni predmet'
    verbose_name_plural = 'Letni predmeti'

class Subject(ProtectedModel):
  code = models.CharField(max_length = 5, primary_key = True, verbose_name = u'Šifrant')
  name = models.CharField(max_length = 200, verbose_name = 'Ime')
  expert = models.BooleanField(default = True, verbose_name = 'Strokovni')
  program_module = models.ForeignKey('core.ProgramModule', blank = True, null = True, verbose_name = 'Modul')
  credit_points = models.DecimalField(default = 0, max_digits = 5, decimal_places = 2, verbose_name = u'Kreditne točke')
  inactive = models.BooleanField(default = False, verbose_name = 'Neveljaven')
   
  def __unicode__(self):
    return u'(%s) %s' %(self.code, self.name)
  
  def number_applies(self, student, till = date.today()):
    result = []
    get_subject_participant = SubjectParticipant.objects.filter(student = student, subject__subject__pk = self.pk)
    get_enrollmentsheet = student.enrollment_sheets.get(academic_year = get_subject_participant[0].subject.school_year(shorter = True))
    print "test %s" %get_enrollmentsheet
    
    queryset_enrollmentsheet = student.enrollment_sheets.filter(student_year = get_enrollmentsheet.student_year).order_by('-academic_year')
    subject_exams_all = Exam.objects.filter(subject__subject = self.pk)
    
    #if repeats
    if queryset_enrollmentsheet[0].enrollment_type == settings.ENROLLMENT_TYPES[1][0]:
      subject_exams = Exam.objects.filter(subject__subject = self.pk, date__gt = date(queryset_enrollmentsheet[0].academic_year_start(),10,1))
    else:
      subject_exams = Exam.objects.filter(subject__subject = self.pk)  
      
    numberApplys_all = ExamApplication.objects.filter(student = student, exam__in = subject_exams_all, exam__date__lte = till).count()
    numberApplys = ExamApplication.objects.filter(student = student, exam__in = subject_exams, exam__date__lte = till).count()
    
    print "vsa polaganja %s" %numberApplys_all
    print "odsteta %s" %numberApplys
    result.append(numberApplys_all)
    result.append(numberApplys_all - numberApplys)
    #first element is  number of all applies
    #second element is number of deducted applies
    return result
    
  def number_applies_str(self, student, till = date.today()):
    l = self.number_applies(student, till)
    if l[1] == 0:
      return str(l[0])
    else:
      return str(l[0])+" - "+str(l[1])
  
  def check_deps(self):
    if self.subjectyear_set.count() > 0:
      return False
    return True
  
  class Meta:
    verbose_name = 'Predmet'
    verbose_name_plural = 'Predmeti'
    ordering = ['name']

class SubjectProgram(models.Model):
  program = models.ForeignKey('core.Program')
  subject = models.ForeignKey('Subject', verbose_name = 'Predmet')
  course_group = models.ForeignKey('core.CourseGroup', blank = True, null = True, verbose_name = 'Smer')
  year = models.PositiveIntegerField(blank = True, null = True, verbose_name = 'Letnik')
  obligatory = models.BooleanField(default = True, verbose_name = 'Obvezen')
  
  class Meta:
    verbose_name = u'Program in smer'
    verbose_name_plural = u'Programi in smeri'
  
  def __unicode__(self):
    return unicode(self.program) + ('-{0}'.format(self.course_group) if self.course_group else '')
  
class SubjectParticipant(models.Model):
  subject = models.ForeignKey('SubjectYear')
  student = models.ForeignKey('student.Student')
  
  def __unicode__(self):
    return '%s, %s' % (unicode(self.subject), unicode(self.student))
  
  class Meta:
    verbose_name = 'Prijava na predmet'
    verbose_name_plural = 'Prijave na predmet'

class StudentModule(models.Model):
  student = models.ForeignKey('student.Student')
  program_module = models.ForeignKey('core.ProgramModule')
  school_year_start = models.IntegerField()
  school_year_end = models.IntegerField()

class Exam(models.Model):
  id = models.CharField(max_length = 32, primary_key = True)
  subject = models.ForeignKey("SubjectYear",verbose_name="Predmet")
  date = models.DateField(default = date.today(),verbose_name="Datum")
  time = models.TimeField(blank = True, null = True,verbose_name="Ura")
  place = models.CharField(null=True,blank = True, max_length = 10,verbose_name="Prostor")
  max_students = models.PositiveIntegerField(default = 100,verbose_name="Maksimalno število študentov")
  created = models.DateTimeField(auto_now_add = True, db_index = True)
  executor = models.ForeignKey("Executor",verbose_name="Profesor")
  special = models.BooleanField(default = False, verbose_name = 'Poseben') #True if is created through Vnos ocene s prijavnice
  max_points = models.PositiveIntegerField(default = 100,verbose_name="Maksimalno število točk izpita")
  max_points_rest = models.PositiveIntegerField(default = 100,verbose_name="Maksimalno število točk ostalo")

#grade_date = models.DateField(null = True, verbose_name = 'Datum ocene')
#  exam_grade = models.PositiveIntegerField(null=True, blank=True, validators = GRADE_VALIDATORS, verbose_name = 'Ocena izpita')
#  practice_grade = models.PositiveIntegerField(null=True, blank=True, validators = GRADE_VALIDATORS, verbose_name = 'Ocena vaj')
#  points = models.PositiveIntegerField(null=True, blank=True, validators = POINTS_VALIDATORS, verbose_name = 'Število točk')
#  points_rest  
  
  def has_results_or_grades(self):
    all = ExamApplication.objects.filter(Q(exam=self),
      Q(
        Q(exam_grade__isnull=False) | 
        Q(practice_grade__isnull=False) |
        Q(points__isnull=False) |
        Q(points_rest__isnull=False)
      ))
    return len(all) > 0
    
  def date_to_school_year(self,d=None):
    if not d:
      d = self.date
    month = d.month
    year = d.year
    if month >= 10:
      return (year,year+1)
    else:
      return (year-1,year)
  
  def __getattr__(self, name):
    """For backwards compatibility.
    """
    if name == 'rating_type':
      return self.subject.rating_type
    return super(Exam, self).__getattr__(name)
  
  def __setattr__(self, name, value):
    """For backwards compatibility
    """
    if name == 'rating_type':
      # Silently ignore
      self.subject.rating_type = value
    else:
      super(Exam, self).__setattr__(name, value)
  
  def __unicode__(self):
    return u"%s - %s" %(self.subject, format(self.date, settings.DATE_FORMAT))
  
  def save(self, *args, **kwargs):
    if self.created is None:
      self.created = date.today()
    if not self.pk:
      self.pk = md5(str(random()) + str(time)+str(self.subject.pk)+str(self.executor.pk)).hexdigest()
    super(Exam, self).save(*args, **kwargs)
  
  def show_professor(self):
    return self.executor.cooperates_with_str()
  
  def num_students(self):
    try:
      return self.exam_applications.count()
    except:
      return 0
    
  def number_applies_till_date(self, student, till = None, app_id=None):
    ea = ExamApplication.objects.filter(student=student,exam__subject__subject=self.subject.subject, exam__date__lte=till)
    ea = ea.exclude(exam_grade=0,practice_grade=0)
    return len(ea)
    
  def number_applies_this_year(self, student):
    getSubject = Subject.objects.get(pk = self.subject.subject.pk) 
    
    counter = 0
    queryset_exams = Exam.objects.filter(subject__subject__pk = getSubject.pk)
    queryset_applications = ExamApplication.objects.filter(student = student, exam__in = queryset_exams)
    
    date_today = datetime.date.today()
    #check number of applications for selected subject in current year
    for j in queryset_applications:
      year_exam = j.exam.date.year
      year_today = date_today.year
      
      if j.exam.date.month < 10:
        year_exam = j.exam.date.year -1
      
      if date_today.month < 10:
        year_today = date_today.year - 1
        
      if year_exam == year_today:
        counter = counter + 1
    return counter
  
  def check_14_days(self, student):
    subject_year_id = self.subject
    queryset_exams_all = Exam.objects.filter(subject=subject_year_id).order_by('-date')
    queryset_apply = ExamApplication.objects.values('exam').filter(student = student, exam__in = queryset_exams_all)
    queryset_apply_exams = Exam.objects.filter(pk__in = queryset_apply).order_by('-date')
    
    if len(queryset_apply_exams)>0:
      if self.date - queryset_apply_exams[0].date < datetime.timedelta(days=14):
        return False
    return True         
  
  def check_exam_date(self):
    date_exam = self.date
    days = 2
    #check for Mondays
    if date_exam.isoweekday() == 1:
      days = 3
    #check for Sundays  
    elif date_exam.isoweekday() == 2:
      days = 1
    #default
    else:
      days = 2
      
    if date_exam - datetime.date.today() <= datetime.timedelta(days = days):
      return False
    else:
      return True 
      
  @staticmethod
  def for_professor(professor):
    executors = Executor.objects.filter(Q(executor__professor = professor) | Q(cooperates_with__isnull = True, professor=professor))
    return Exam.objects.filter(executor__in=executors)
  
  class Meta:
    unique_together = (
      ('subject', 'date', 'executor', 'special',),
    )
    verbose_name = "Izpitni rok"

class ExamApplication(models.Model):
  GRADE_VALIDATORS = [
    MinValueValidator(settings.VALID_GRADE_RANGE[0]),
    MaxValueValidator(settings.VALID_GRADE_RANGE[1])
  ]
  
  POINTS_VALIDATORS = [
    MinValueValidator(settings.VALID_POINTS_RANGE[0]),
    MaxValueValidator(settings.VALID_POINTS_RANGE[1])
  ]
  
  exam = models.ForeignKey("Exam", related_name = 'exam_applications')
  student = models.ForeignKey("student.Student")
  application_date = models.DateField()
  grade_date = models.DateField(null = True, verbose_name = 'Datum ocene')
  exam_grade = models.PositiveIntegerField(null=True, blank=True, validators = GRADE_VALIDATORS, verbose_name = 'Ocena izpita')
  practice_grade = models.PositiveIntegerField(null=True, blank=True, validators = GRADE_VALIDATORS, verbose_name = 'Ocena vaj')
  points = models.PositiveIntegerField(null=True, blank=True, validators = POINTS_VALIDATORS, verbose_name = 'Število točk')
  points_rest = models.PositiveIntegerField(null=True, blank=True, validators = POINTS_VALIDATORS, verbose_name = 'Število točk ostalo')
  paid = models.BooleanField(default = False, verbose_name = 'Plačilo izpita')
  
  def clean(self):
    super(ExamApplication, self).clean()
    
    # Check if both grades are entered, that both are positive or both negative
    #if self.exam_grade is not None and self.final_grade is not None:
    #  if (self.writen_grade - settings.POSITIVE_GRADE_BOUNDARY) * (self.oral_grade - settings.POSITIVE_GRADE_BOUNDARY) < 0:
    #    raise ValidationError(u'Obe oceni morata biti ali pozitivni ali negativni')
       
    if self.grade_date:
      if not self.exam_grade and not self.practice_grade:
        raise ValidationError(u'Vnesti je potrebno oceno!')
      if self.exam.subject.rating_type == 'OC1':
        if not (self.exam_grade and self.practice_grade):
          raise ValidationError(u'Vnesti je potrebno oceni!')
        else:
          if not((self.exam_grade >= settings.POSITIVE_GRADE_BOUNDARY and self.practice_grade >= settings.POSITIVE_GRADE_BOUNDARY) or (self.exam_grade < settings.POSITIVE_GRADE_BOUNDARY and self.practice_grade < settings.POSITIVE_GRADE_BOUNDARY)):
            raise ValidationError(u'Obe oceni pozitivni ali obe oceni negativni!') 
      elif self.exam.subject.rating_type == 'OC2':
        if not self.practice_grade:
          raise ValidationError(u'Vnesti je potrebno oceno vaj!')
      elif self.exam.subject.rating_type == 'OC3':
        if not self.exam_grade:
          raise ValidationError(u'Vnesti je potrebno oceno izpita!')
    
    # Check if there can be any more applications to the exam
    if self.exam.num_students() >= self.exam.max_students:
      raise ValidationError(u'Na ta izpit se ne moreš več prijaviti')

  def get_average_grade(self):
    if self.exam.subject.rating_type == 'OC1':
      return (self.exam_grade + self.practice_grade) / float(2)
    if self.exam.subject.rating_type == 'OC2':
      return self.practice_grade
    if self.exam.subject.rating_type == 'OC3':
      return self.exam_grade
      
  class Meta:
    permissions = (
      ('can_exam_list', u'Lahko izpisuje seznam prijavljenih na predmet'),
      ('can_exam_partial_final_list', u'Lahko izpisuje seznam delnih in koncnih ocen'),
      ('can_view_grades', u'Izpis ocen')
    )